
from vrpsc.Util.operators import *
from vrpsc.Util.generate_init_solution import generate_solution_greedy
from vrpsc.Util.util import *
from vrpsc.Util.load_data import *
from vrpsc.ALNS import _calculate_and_package_metrics


def destruct_construct(current_solution, d_num):
    destroyed_info = destroy_couple_random(current_solution, d_num)
    new_solution = repair_couple_greedy(*destroyed_info, current_solution)
    return new_solution


def local_search(solution, start_t, duration):
    sequence_map = solution.get_sequence_map()
    path_init_task_map = solution.get_path_init_task_map()
    task_list = list(range(1, solution.task_num + 1))
    random.shuffle(task_list)
    for task in task_list:  # 每个任务都greedy搜索一遍
        if time.time() - start_t > duration:
            return Solution(solution.instance, sequence_map, path_init_task_map)

        remove_(sequence_map, path_init_task_map, task)
        greedy_sequence_map = None
        greedy_path_init_task_map = None
        fitness_min = 1e6

        parent_position_set = get_all_position(sequence_map, path_init_task_map, task, 'parent')
        for parent_position in parent_position_set:
            destroyed_sequence_map_temp = copy_dict_int_dict(sequence_map)
            path_init_task_map_temp = copy_dict_int_int(path_init_task_map)
            insert_(destroyed_sequence_map_temp, path_init_task_map_temp, task, parent_position, 'parent')
            child_position_set = get_feasible_insert_position(destroyed_sequence_map_temp, path_init_task_map_temp,
                                                              task, 'child')
            for child_position in child_position_set:
                destroyed_sequence_map_temp_temp = copy_dict_int_dict(destroyed_sequence_map_temp)
                path_init_task_map_temp_temp = copy_dict_int_int(path_init_task_map_temp)
                insert_(destroyed_sequence_map_temp_temp, path_init_task_map_temp_temp, task, child_position,
                        'child')
                fitness, _, _ = cal_fitness(solution.instance, destroyed_sequence_map_temp_temp,
                                            path_init_task_map_temp_temp)
                if fitness < fitness_min:
                    greedy_sequence_map = destroyed_sequence_map_temp_temp
                    greedy_path_init_task_map = path_init_task_map_temp_temp
                    fitness_min = fitness
        sequence_map = greedy_sequence_map
        path_init_task_map = greedy_path_init_task_map
    new_solution = Solution(solution.instance, sequence_map, path_init_task_map)
    return new_solution


def IGA(instance_name):
    instance = read_excel(instance_name)

    task_num = len(instance)
    d_num = math.ceil(task_num * 0.3)

    solution = generate_solution_greedy(instance)

    current_fitness = solution.get_fitness()
    # print(f"初始解的fitness:{current_fitness}")

    best_solution = solution
    best_fitness = current_fitness
    count = 0
    fitness_time_list = [[current_fitness, 0]]
    start_t = time.time()

    CONSTANT_T = 0.2
    # print(f"温度:{CONSTANT_T}")

    duration = task_num ** 2 * (Config.M_child + Config.M_parent) * 10 / 1000
    # print(f"运行duration:{duration}")

    while time.time() - start_t < duration:
        count += 1
        d_c_solution = destruct_construct(solution, d_num)
        new_solution = local_search(d_c_solution, start_t, duration)

        new_fitness = new_solution.get_fitness()
        if new_fitness < current_fitness:
            solution = new_solution
            current_fitness = new_fitness
            if new_fitness < best_fitness:
                best_solution = new_solution
                best_fitness = new_fitness
                fitness_time_list.append([best_fitness, time.time() - start_t])
        elif new_fitness == current_fitness:
            pass
        else:
            p_a = math.exp((current_fitness - new_fitness) / CONSTANT_T)
            print(f"p:{p_a}")
            if random.random() < p_a:
                solution = new_solution
                current_fitness = new_fitness

        print(f"第{count}次迭代，最优fitness为：{round(best_fitness, 4)} 当前fitness为 ：{round(current_fitness, 4)} 新fitness：{round(new_fitness, 4)}  time:{round(time.time() - start_t, 4)}")
    print(f"best fitness:{best_fitness}  best solution:{best_solution}")

    return best_solution


def run_IGA_experiment(instance_name, repeated):
    runtime_start_total = time.time()

    # Lists to store metrics for ALNS solutions
    costs = []
    delay_costs = []
    dist_costs = []


    best_cost = float('inf')  # Initialize with a very large number
    best_solution_code = None  # Stores the actual code/representation of the best ALNS solution

    print(f"Starting IGA for instance: {instance_name} with {repeated} repetitions...")

    for i in range(repeated):
        print(f"--- Repetition {i + 1}/{repeated} ---")

        # Execute the ALNS algorithm for the current repetition
        solution = IGA(instance_name)

        # Process ALNS solution metrics
        costs.append(solution.get_fitness())
        dist_costs.append(solution.distance)
        delay_costs.append(solution.tardiness)

        # Update best_alns_cost and best_alns_solution_code if the current ALNS solution is better
        if solution.get_fitness() < best_cost:
            best_cost = solution.get_fitness()
            best_solution_code = solution.get_code()  # Store the code of the best ALNS solution found so far

    total_runtime = time.time() - runtime_start_total
    avg_runtime_per_repetition = total_runtime / repeated

    # Calculate and package metrics for ALNS solutions
    data_package = _calculate_and_package_metrics(
        costs,
        dist_costs,
        delay_costs,
        avg_runtime_per_repetition
    )
    # Update the first element of alns_data_package with the best solution code
    data_package[0] = best_solution_code
    return best_solution_code, data_package


# if __name__ == "__main__":
#     instance_name = "tasks_50"
#     solution = IGA(instance_name)
#     print(f"fitness:{solution.get_fitness()}")
#     print(f"distance:{solution.distance}  tardiness:{solution.tardiness}")
#     pass
